package cn.signalpha.modules.pay.granter;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.hutool.core.util.URLUtil;
import cn.hutool.json.JSONUtil;
import cn.signalpha.common.cache.ConfigCache;
import cn.signalpha.common.cache.ConfigKeys;
import cn.signalpha.common.dto.ConfigDTO;
import cn.signalpha.core.log.exception.ServiceException;
import cn.signalpha.core.tool.jackson.JsonUtil;
import cn.signalpha.core.tool.support.Kv;
import cn.signalpha.core.tool.utils.StringPool;
import cn.signalpha.core.tool.utils.StringUtil;
import cn.signalpha.modules.pay.dto.PayDTO;
import cn.signalpha.modules.pay.dto.RefundDTO;
import cn.signalpha.modules.pay.dto.SettleOrderDTO;
import cn.signalpha.modules.pay.dto.SettleUserDTO;
import cn.signalpha.modules.pay.enums.PayClientEnum;
import cn.signalpha.modules.pay.provider.IPayGranter;
import cn.signalpha.modules.pay.vo.RefundVO;
import cn.signalpha.modules.pay.vo.SettleOrderVO;
import cn.signalpha.modules.pay.vo.SettleUserVO;
import cn.signalpha.modules.shop.entity.Order;
import cn.signalpha.modules.shop.entity.OrderSettle;
import cn.signalpha.modules.shop.entity.Seller;
import cn.signalpha.modules.shop.service.OrderService;
import cn.signalpha.modules.shop.service.OrderSettleService;
import cn.signalpha.modules.shop.service.SellerService;
import com.ijpay.core.IJPayHttpResponse;
import com.ijpay.core.enums.RequestMethodEnum;
import com.ijpay.core.enums.SignType;
import com.ijpay.core.kit.PayKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.WxPayApiConfig;
import com.ijpay.wxpay.WxPayApiConfigKit;
import com.ijpay.wxpay.enums.WxDomainEnum;
import com.ijpay.wxpay.enums.v3.BasePayApiEnum;
import com.ijpay.wxpay.model.v3.*;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.service.WxOAuth2Service;
import me.chanjar.weixin.mp.api.WxMpService;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.*;

/**
 * 微信支付 合单支付
 *
 * @author signalpha
 */
@Slf4j
@Component
@AllArgsConstructor
public class WxPayV3HDGranter implements IPayGranter {

	private final WxMpService wxMpService;
	private final WxMaService wxMaService;
	private final OrderService orderService;
	private final SellerService sellerService;
	private final OrderSettleService orderSettleService;

	@Override
	public String getPayVO(PayDTO payDTO) {
		ConfigDTO.WxPayDTO config = ConfigCache.getObject(ConfigKeys.WX_PAY, ConfigDTO.WxPayDTO.class);
		payDTO.setNotifyUrl(config.getDomain().concat(payDTO.getNotifyUrl()).concat("/wx/hd"));
		switch (Objects.requireNonNull(PayClientEnum.instance(payDTO.getPayClient()), "支付终端错误")){
			case H5:
				return wxH5Pay(payDTO);
			case PUBLIC:
				return wxPublicPay(payDTO);
			case MINI_APP:
				return wxMiniAppPay(payDTO);
			default:
				throw new ServiceException("终端未实现");
		}
	}

	@Override
	public String verifyNotify(Map<String, String> params) {
		return StringPool.FALSE;
	}

	@Override
	@SneakyThrows
	public RefundVO refund(RefundDTO refundDTO) {
		refundDTO.setNotifyUrl(refundDTO.getNotifyUrl().concat("/wx"));
		int totalFee = BigDecimal.valueOf(refundDTO.getTotalMoney()).movePointRight(2).intValue();
		int refundFee = BigDecimal.valueOf(refundDTO.getRefundMoney()).movePointRight(2).intValue();
		WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();

		List<RefundGoodsDetail> list = new ArrayList<>();
		RefundGoodsDetail refundGoodsDetail = new RefundGoodsDetail()
			.setMerchant_goods_id("123")
			.setGoods_name("IJPay 测试")
			.setUnit_price(1)
			.setRefund_amount(1)
			.setRefund_quantity(1);
		list.add(refundGoodsDetail);

		RefundModel refundModel = new RefundModel()
			.setOut_refund_no(refundDTO.getPayBizNo())
			.setTransaction_id(refundDTO.getPayBizNo())
			.setOut_trade_no(refundDTO.getPayNo())
			.setReason("退款")
			.setNotify_url(refundDTO.getNotifyUrl())
			.setAmount(new RefundAmount().setRefund(refundFee).setTotal(totalFee).setCurrency("CNY"))
			.setGoods_detail(list);

		IJPayHttpResponse response = WxPayApi.v3(
			RequestMethodEnum.POST,
			WxDomainEnum.CHINA.toString(),
			BasePayApiEnum.REFUND.toString(),
			wxPayApiConfig.getMchId(),
			String.valueOf(wxPayApiConfig.getExParams()),
			null,
			wxPayApiConfig.getKeyPath(),
			JSONUtil.toJsonStr(refundModel)
		);

		boolean verifySignature = WxPayKit.verifySignature(response, wxPayApiConfig.getPlatformCertPath());
		if(!verifySignature){
			throw new ServiceException("退款验签失败，请检查平台证书是否正确");
		}
		Map<String, Object> responseMap = JsonUtil.toMap(response.getBody());
		return RefundVO.builder().code(StringPool.YES).orderBizNo(String.valueOf(responseMap.get("refund_id"))).build();
	}

	@Override
	public SettleUserVO settleUser(SettleUserDTO settleUserDTO) {
		return SettleUserVO.builder().code(StringPool.NO).msg("该支付未实现分账").build();
	}

	@Override
	public SettleOrderVO settleOrder(SettleOrderDTO settleOrderDTO) {
		return SettleOrderVO.builder().code(StringPool.NO).msg("该支付未实现分账").build();
	}

	/**
	 * 微信H5 支付
	 */
	@SneakyThrows
	private String wxH5Pay(PayDTO payDTO) {
		if(StringUtil.isBlank(payDTO.getAppId())) {
			throw new ServiceException("appId不能为空");
		}

		int totalFee = new BigDecimal(payDTO.getPayMoney()).movePointRight(2).intValue();
		WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();
		UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
			.setAppid(payDTO.getAppId())
			.setMchid(wxPayApiConfig.getMchId())
			.setDescription(payDTO.getBody())
			.setOut_trade_no(PayKit.generateStr())
			.setAttach(payDTO.getNotifyParams())
			.setNotify_url(payDTO.getNotifyUrl())
			.setAmount(new Amount().setTotal(totalFee))
			.setScene_info(
				new SceneInfo()
					.setPayer_client_ip(payDTO.getIp()).
					setH5_info(new H5Info()
						.setType("Wap")));

		IJPayHttpResponse response = WxPayApi.v3(
			RequestMethodEnum.POST,
			WxDomainEnum.CHINA.toString(),
			BasePayApiEnum.H5_PAY.toString(),
			wxPayApiConfig.getMchId(),
			String.valueOf(wxPayApiConfig.getExParams()),
			null,
			wxPayApiConfig.getKeyPath(),
			JsonUtil.toJson(unifiedOrderModel)
		);
		if(response.getStatus() != 200){
			Map<String, Object> responseMap = JsonUtil.toMap(response.getBody());
			throw new ServiceException(StringUtil.format("拉起支付失败[{}]", responseMap.get("message")));
		}
		boolean verifySignature = WxPayKit.verifySignature(response, wxPayApiConfig.getPlatformCertPath());
		if(!verifySignature){
			throw new ServiceException("支付验签失败，请检查平台证书是否正确");
		}
		Map<String, Object> responseMap = JsonUtil.toMap(response.getBody());
		String webUrl = String.valueOf(responseMap.get("mweb_url"));
		return StringUtil.builder(webUrl, "&redirect_url=", URLUtil.encode(payDTO.getReturnUrl())).toString();
	}

	/**
	 * 微信公众号 支付
	 */
	@SneakyThrows
	private String wxPublicPay(PayDTO payDTO) {
		if(StringUtil.isAnyBlank(payDTO.getCode(), payDTO.getAppId())) {
			throw new ServiceException("appId或code不能为空");
		}

		String openId;
		WxOAuth2Service oAuth2Service = wxMpService.switchoverTo(payDTO.getAppId()).getOAuth2Service();
		try {
			openId = oAuth2Service.getAccessToken(payDTO.getCode()).getOpenId();
		} catch (WxErrorException e) {
			throw new ServiceException(e.getError().getErrorMsg());
		}

		int totalFee = new BigDecimal(payDTO.getPayMoney()).movePointRight(2).intValue();
		WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();
		UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
			.setAppid(payDTO.getAppId())
			.setMchid(wxPayApiConfig.getMchId())
			.setDescription(payDTO.getBody())
			.setOut_trade_no(PayKit.generateStr())
			.setAttach(payDTO.getNotifyParams())
			.setNotify_url(payDTO.getNotifyUrl())
			.setAmount(new Amount().setTotal(totalFee))
			.setPayer(new Payer().setOpenid(openId));

		IJPayHttpResponse response = WxPayApi.v3(
			RequestMethodEnum.POST,
			WxDomainEnum.CHINA.toString(),
			BasePayApiEnum.JS_API_PAY.toString(),
			wxPayApiConfig.getMchId(),
			String.valueOf(wxPayApiConfig.getExParams()),
			null,
			wxPayApiConfig.getKeyPath(),
			JsonUtil.toJson(unifiedOrderModel)
		);
		if(response.getStatus() != 200){
			Map<String, Object> responseMap = JsonUtil.toMap(response.getBody());
			throw new ServiceException(StringUtil.format("拉起支付失败[{}]", responseMap.get("message")));
		}
		boolean verifySignature = WxPayKit.verifySignature(response, wxPayApiConfig.getPlatformCertPath());
		if(!verifySignature){
			throw new ServiceException("支付验签失败，请检查平台证书是否正确");
		}
		Map<String, Object> responseMap = JsonUtil.toMap(response.getBody());
		String prepayId = String.valueOf(responseMap.get("prepay_id"));
		return JsonUtil.toJson(WxPayKit.jsApiCreateSign(payDTO.getAppId(), prepayId, wxPayApiConfig.getKeyPath()));
	}

	/**
	 * 微信小程序 支付
	 */
	@SneakyThrows
	private String wxMiniAppPay(PayDTO payDTO) {
		if(StringUtil.isAnyBlank(payDTO.getCode(), payDTO.getAppId())) {
			throw new ServiceException("appId或code不能为空");
		}

//		String openId;
//		try {
//			openId = wxMaService.getUserService().getSessionInfo(payDTO.getCode()).getOpenid();
//		} catch (WxErrorException e) {
//			throw new ServiceException(e.getError().getErrorMsg());
//		}

		//把订单重新拉出来
		WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();
		List<Map<String, Object>> subOrderList = new ArrayList<>();
		List<OrderSettle> orderSettleList = orderSettleService.getByPayNo(payDTO.getPayNo());
		for(OrderSettle orderSettle : orderSettleList){
			Order order = orderService.getByOrderNo(orderSettle.getOrderNo());
			Seller seller = sellerService.getBaseById(orderSettle.getSellerId());
			int totalAmount = BigDecimal.valueOf(orderSettle.getSettleAmount()).movePointRight(2).intValue();
			Map<String, Object> subOrder = new HashMap<>();
			subOrder.put("mchid", wxPayApiConfig.getMchId());
			subOrder.put("sub_mchid", seller.getServerNo());
			subOrder.put("attach", seller.getId().toString());
			subOrder.put("amount", Kv.create().set("total_amount", totalAmount).set("currency", "CNY"));
			subOrder.put("out_trade_no", orderSettle.getOrderNo());
			subOrder.put("description", order.getGoodsName().length() > 100 ? order.getGoodsName().substring(0, 100) : order.getGoodsName());
			subOrder.put("settle_info", Kv.create().set("profit_sharing", true));
			subOrderList.add(subOrder);
		}

		Map<String, Object> bodyMap = new HashMap<>();
		bodyMap.put("combine_appid", payDTO.getAppId());
		bodyMap.put("combine_out_trade_no", payDTO.getPayNo());
		bodyMap.put("combine_mchid", wxPayApiConfig.getMchId());
		bodyMap.put("combine_payer_info", Kv.create().set("openid", payDTO.getCode()));
		bodyMap.put("notify_url", payDTO.getNotifyUrl());
		bodyMap.put("sub_orders", subOrderList);

		IJPayHttpResponse response = WxPayApi.v3(
			RequestMethodEnum.POST,
			WxDomainEnum.CHINA.toString(),
			BasePayApiEnum.COMBINE_TRANSACTIONS_JS.toString(),
			wxPayApiConfig.getMchId(),
			String.valueOf(wxPayApiConfig.getExParams()),
			null,
			wxPayApiConfig.getKeyPath(),
			JSONUtil.toJsonStr(bodyMap)
		);
		if(response.getStatus() != 200){
			Map<String, Object> responseMap = JsonUtil.toMap(response.getBody());
			throw new ServiceException(StringUtil.format("拉起支付失败[{}]", responseMap.get("message")));
		}
		boolean verifySignature = WxPayKit.verifySignature(response, wxPayApiConfig.getPlatformCertPath());
		if(!verifySignature){
			throw new ServiceException("支付验签失败，请检查平台证书是否正确");
		}
		Map<String, Object> responseMap = JsonUtil.toMap(response.getBody());
		String prepayId = String.valueOf(responseMap.get("prepay_id"));
		return JsonUtil.toJson(WxPayKit.miniAppPrepayIdCreateSign(payDTO.getAppId(), prepayId, wxPayApiConfig.getKeyPath(), SignType.HMACSHA256));
	}

}
